﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Xml.Linq;
using Microsoft.Win32;
using Utility3D;

namespace Modeller
{

    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow
    {
        private WorldModel _worldModel;
        private readonly List<MyCanvas> _canvases = new List<MyCanvas>();
        private MyCanvas _selectedCanvas;

        public MainWindow()
        {
            _worldModel = DefaultWorldModelGenerator.GetModel();

            InitializeComponent();
        }

        private static void HandleException(Exception exception)
        {
            MessageBox.Show(exception.Message);
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            if (_selectedCanvas == null) return;
            try
            {
                _selectedCanvas.ProcessMouseMove(e.GetPosition(_selectedCanvas));
                base.OnMouseMove(e);
            }
            catch (Exception exception)
            {
                HandleException(exception);
            }
        }

        protected override void OnKeyDown(KeyEventArgs e)
        {
            if (_selectedCanvas == null) return;

            switch (e.Key)
            {
                case Key.C:
                    _selectedCanvas.ClosePoint();
                    break;
                case Key.E:
                    _selectedCanvas.Extend();
                    break;
                case Key.J:
                    _selectedCanvas.Join();
                    break;
            }
            base.OnKeyDown(e);
        }

        protected override void OnMouseDown(MouseButtonEventArgs e)
        {
            try
            {
                var selectedCanvas = _canvases
                    .Select(canvas => new {Canvas = canvas, Point = e.GetPosition(canvas)})
                    .Where(a => a.Point.X >= 0 && a.Point.X < a.Canvas.ActualWidth && a.Point.Y >= 0 && a.Point.Y < a.Canvas.ActualHeight)
                    .FirstOrDefault();

                if (selectedCanvas == null) return;

                _selectedCanvas = selectedCanvas.Canvas;
                selectedCanvas.Canvas.ProcessMouseDown(selectedCanvas.Point);

                base.OnMouseDown(e);
            }
            catch (Exception exception)
            {
                HandleException(exception);
            }
        }

        protected override void OnMouseUp(MouseButtonEventArgs e)
        {
            if (_selectedCanvas == null) return;
            try
            {
                _selectedCanvas.ProcessMouseUp();
                base.OnMouseUp(e);
            }
            catch (Exception exception)
            {
                HandleException(exception);
            }
        }

        private CanvasWrapper GetNewCanvasWrapper()
        {
            var newCanvas = new CanvasWrapper();
            InitializeCanvas(newCanvas);
            newCanvas.CloseAction = wrapper => CloseCanvas(wrapper);

            MainGrid.Children.Add(newCanvas);
            return newCanvas;
        }

        private void InitializeCanvas(CanvasWrapper newCanvas)
        {
            _canvases.Add(newCanvas.Canvas);
            newCanvas.Canvas.DrawingSurface = _worldModel.DrawingSurfaceChains.First();
            newCanvas.Canvas.InvalidateCanvasVisuals = InvalidateCanvasVisuals;
            newCanvas.Canvas.RecalculateGeometries = RecalculateGeometries;
            newCanvas.Canvas.SurfaceCursors = MainGrid
                .Children
                .OfType<CanvasWrapper>()
                .Where(wrapper => wrapper.Canvas.DrawingSurface != newCanvas.Canvas.DrawingSurface)
                .Select(wrapper => wrapper.Canvas.DrawingSurface);
        }

        private void WindowLoaded(object sender, RoutedEventArgs e)
        {
            var newCanvas = DefaultCanvas;
            InitializeCanvas(newCanvas);
            _selectedCanvas = DefaultCanvas.Canvas;
        }

        private void New(object sender, RoutedEventArgs e)
        {
            _worldModel = DefaultWorldModelGenerator.GetModel();
            var drawingSurface = _worldModel.DrawingSurfaceChains.First();

            foreach (var canvasWrapper in MainGrid.Children.OfType<CanvasWrapper>())
            {
                canvasWrapper.Canvas.DrawingSurface = drawingSurface;
                canvasWrapper.UserControlLoaded(null, null);
            }
        }

        private void SaveAs(object sender, RoutedEventArgs e)
        {
            var dialog = new SaveFileDialog {Filter = "XML | *.xml", DefaultExt = "xml"};
            if (dialog.ShowDialog().Value)
                SaveFile(dialog.FileName);
        }

        private void SaveFile(string fileName)
        {
            _worldModel.ToXml().Save(fileName);
        }

        private void Open(object sender, RoutedEventArgs e)
        {
            try
            {
                var dialog = new OpenFileDialog {Filter = "XML | *.xml", DefaultExt = "xml"};
                if (dialog.ShowDialog().Value)
                    OpenFile(dialog.FileName);
            }
            catch (Exception exception)
            {
                HandleException(exception);
            }
        }

        private void OpenFile(string fileName)
        {
            var worldModelElement = XElement.Load(fileName);
            var worldModel = new WorldModel();
            ModelObjectXmlSerializer.PopulateWorldModel(worldModelElement, worldModel);

            _worldModel = worldModel;
        }

        private void UpdateGrid()
        {
            Grid.SetRowSpan(ControlPanel, Grid.GetRowSpan(ControlPanel) + 1);
            Grid.SetColumn(ControlPanel, MainGrid.ColumnDefinitions.Count);
            Grid.SetColumnSpan(MainMenu, MainGrid.ColumnDefinitions.Count);
        }

        private void HorizontalSplitButtonClick(object sender, RoutedEventArgs e)
        {
            MainGrid.RowDefinitions.Add(new RowDefinition {Height = new GridLength(1, GridUnitType.Star)});
            UpdateGrid();
            CollapseGridSlots(true);
        }

        private void VerticalSplitButtonClick(object sender, RoutedEventArgs e)
        {
            MainGrid.ColumnDefinitions.Insert(0, new ColumnDefinition {Width = new GridLength(1, GridUnitType.Star)});
            UpdateGrid();
            CollapseGridSlots(true);
        }

        private void CloseCanvas(CanvasWrapper canvasWrapper)
        {
            _canvases.Remove(canvasWrapper.Canvas);
            MainGrid.Children.Remove(canvasWrapper);

            var columns = MainGrid.ColumnDefinitions.Count - 1;
            var rows = MainGrid.RowDefinitions.Count - 1;

            var reducedColumnSlots = (columns - 1)*rows;
            var reducedRowSlots = (rows - 1)*columns;

            if (reducedRowSlots >= _canvases.Count && (reducedRowSlots <= reducedColumnSlots || reducedColumnSlots < _canvases.Count))
                MainGrid.RowDefinitions.RemoveAt(MainGrid.RowDefinitions.Count - 1);
            else if (reducedColumnSlots >= _canvases.Count)
                MainGrid.ColumnDefinitions.RemoveAt(0);

            UpdateGrid();
            CollapseGridSlots(false);
        }

        private void CollapseGridSlots(bool fillInEmptySlots)
        {
            var rowCount = MainGrid.RowDefinitions.Count - 1;
            var columnCount = MainGrid.ColumnDefinitions.Count - 1;
            var canvases = MainGrid
                .Children
                .OfType<CanvasWrapper>()
                .OrderBy(canvas => new Point(Grid.GetColumn(canvas), Grid.GetRow(canvas)), new SlotComparer()).ToList();

            foreach(var row in Enumerable.Range(0, rowCount))
                foreach (var column in Enumerable.Range(0, columnCount))
                {
                    var item = row*columnCount + column;
                    if (item >= canvases.Count)
                    {
                        if (!fillInEmptySlots) return;
                        var canvas = GetNewCanvasWrapper();
                        Grid.SetRow(canvas, row + 1);
                        Grid.SetColumn(canvas, column);
                        continue;
                    }

                    Grid.SetRow(canvases[item], row + 1);
                    Grid.SetColumn(canvases[item], column);
                }
        }

        private class SlotComparer : IComparer<Point>
        {
            public int Compare(Point firstPosition, Point secondPosition)
            {
                return firstPosition.Y == secondPosition.Y
                           ? firstPosition.X.CompareTo(secondPosition.X)
                           : firstPosition.Y.CompareTo(secondPosition.Y);
            }
        }

        private void InvalidateCanvasVisuals()
        {
            foreach (var canvas in _canvases)
                canvas.InvalidateVisual();
        }

        private void RecalculateGeometries()
        {
            foreach (var canvas in _canvases)
                canvas.RecalculateGeometry();
        }
    }
}